package com.francis.spring.framework.context;

import com.francis.spring.framework.annotation.FrancisAutowired;
import com.francis.spring.framework.annotation.FrancisService;
import com.francis.spring.framework.aop.FrancisAopProxy;
import com.francis.spring.framework.aop.FrancisCglibAopProxy;
import com.francis.spring.framework.aop.FrancisJdkDynamicAopProxy;
import com.francis.spring.framework.aop.config.FrancisAopConfig;
import com.francis.spring.framework.aop.support.FrancisAdvisedSupport;
import com.francis.spring.framework.beans.FrancisBeanWrapper;
import com.francis.spring.framework.beans.config.FrancisBeanDefinition;
import com.francis.spring.framework.beans.config.FrancisBeanPostProcessor;
import com.francis.spring.framework.beans.support.FrancisBeanDefinitionReader;
import com.francis.spring.framework.beans.support.FrancisDefaultListableBeanFactory;
import com.francis.spring.framework.core.FrancisBeanFactory;
import com.francis.spring.framework.annotation.FrancisController;
import org.springframework.aop.framework.AdvisedSupport;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author Francis
 * @Date 2020/11/15 16:48
 * @Version 1.0
 * IOC、DI、MVC、AOP
 */
public class FrancisApplicationContext extends FrancisDefaultListableBeanFactory implements FrancisBeanFactory {
    private String [] configLoactions;
    private FrancisBeanDefinitionReader reader;
    //单例的IOC容器缓存
    private Map<String,Object> singletonObjects = new ConcurrentHashMap<String, Object>();
    //通用的IOC容器
    private Map<String, FrancisBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, FrancisBeanWrapper>();

    public FrancisApplicationContext(String... configLoactions){
        this.configLoactions = configLoactions;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void refresh() throws Exception {
        //1、定位，定位配置文件
        reader = new FrancisBeanDefinitionReader(this.configLoactions);
        //2、加载配置文件，扫描相关的类，把它们封装成BeanDefinition
        List<FrancisBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        //3、注册，把配置信息放到容器里面(伪IOC容器)
        doRegisterBeanDefinition(beanDefinitions);
        //4、把不是延时加载的类，有提前初始化
        doAutowrited();
    }
    /**
     * 只处理懒加载
     */
    private void doAutowrited() {
        for (Map.Entry<String, FrancisBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if(!beanDefinitionEntry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 注册
     */
    private void doRegisterBeanDefinition(List<FrancisBeanDefinition> beanDefinitions) throws Exception {
        for (FrancisBeanDefinition beanDefinition: beanDefinitions) {
            if(super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
        }
    }
    /**
     * 从这里开始依赖注入，通过读取BeanDefinition中的信息，然后通过反射机制创建一个实例并返回
     * 在Spring中，不会将原始的对象返回去，通过BeanWrapper包装返回
     * 装饰器模式：保留原来的OOP关系
     */
    @Override
    public Object getBean(String beanName) throws Exception {
        FrancisBeanDefinition francisBeanDefinition = this.beanDefinitionMap.get(beanName);
        Object instance = null;
        // 工厂模式 + 策略模式
        FrancisBeanPostProcessor postProcessor = new FrancisBeanPostProcessor();
        postProcessor.postProcessBeforeInitialization(instance,beanName);
        // 初始化
        instance = instantiateBean(beanName,francisBeanDefinition);
        // 把这个对象封装到BeanWrapper中
        FrancisBeanWrapper beanWrapper = new FrancisBeanWrapper(instance);
        // 拿到BeanWraoper之后，把BeanWrapper保存到IOC容器中去
        this.factoryBeanInstanceCache.put(beanName,beanWrapper);
        postProcessor.postProcessAfterInitialization(instance,beanName);
       // 注入
        populateBean(beanName,new FrancisBeanDefinition(),beanWrapper);

        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }
    /**
     * 初始化
     */
    private Object instantiateBean(String beanName, FrancisBeanDefinition beanDefinition) {
        //1、拿到要实例化的对象的类名
        String className = beanDefinition.getBeanClassName();
        //2、反射实例化，得到一个对象
        Object instance = null;
        try {
            //假设默认就是单例
            if(this.singletonObjects.containsKey(className)){
                instance = this.singletonObjects.get(className);
            }else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();
                FrancisAdvisedSupport config = instantionAopConfig(beanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);

                //符合PointCut的规则的话，闯将代理对象
                if(config.pointCutMatch()) {
                    instance = createProxy(config).getProxy();
                }
                this.singletonObjects.put(className,instance);
                this.singletonObjects.put(beanDefinition.getFactoryBeanName(),instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return instance;
    }
    /**
     * 注入
     */
    private void populateBean(String beanName, FrancisBeanDefinition francisBeanDefinition, FrancisBeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrappedInstance();
        Class<?> clazz = beanWrapper.getWrappedClass();
        //判断只有加了注解的类，才执行依赖注入
        if(!(clazz.isAnnotationPresent(FrancisController.class) || clazz.isAnnotationPresent(FrancisService.class))){
            return;
        }
        //获得所有的fields
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(FrancisAutowired.class)){ continue;}
            FrancisAutowired autowired = field.getAnnotation(FrancisAutowired.class);
            String autowiredBeanName =  autowired.value().trim();
            if("".equals(autowiredBeanName)){
                autowiredBeanName = field.getType().getName();
            }
            //强吻
            field.setAccessible(true);
            try {
                if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }
                field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    }
    private FrancisAopProxy createProxy(FrancisAdvisedSupport config) {

        Class targetClass = config.getTargetClass();
        if(targetClass.getInterfaces().length > 0){
            return new FrancisJdkDynamicAopProxy(config);
        }
        return new FrancisCglibAopProxy(config);
    }

    private FrancisAdvisedSupport instantionAopConfig(FrancisBeanDefinition gpBeanDefinition) {
        FrancisAopConfig config = new FrancisAopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAround(this.reader.getConfig().getProperty("aspectAround"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new FrancisAdvisedSupport(config);
    }
    @Override
    public Object getBean(Class<?> beanClass) throws Exception {
        return getBean(beanClass.getName());
    }
    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new  String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount(){
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }
}
